package com.tonyodev.fetch2.downloader;

import ohos.aafwk.ability.DataAbilityRemoteException;
import ohos.utils.net.Uri;

import com.tonyodev.fetch2.Download;
import com.tonyodev.fetch2.EnqueueAction;
import com.tonyodev.fetch2.Error;
import com.tonyodev.fetch2.FetchErrorUtils;
import com.tonyodev.fetch2.ResourceTable;
import com.tonyodev.fetch2.database.DownloadInfo;
import com.tonyodev.fetch2.exception.FetchException;
import com.tonyodev.fetch2.helper.FileDownloaderDelegate;
import com.tonyodev.fetch2.provider.NetworkInfoProvider;
import com.tonyodev.fetch2.util.Defaults;
import com.tonyodev.fetch2.util.FetchUtils;
import com.tonyodev.fetch2.util.TypeConverterExtensions;
import com.tonyodev.fetch2core.AverageCalculator;
import com.tonyodev.fetch2core.DownloadBlock;
import com.tonyodev.fetch2core.DownloadBlockInfo;
import com.tonyodev.fetch2core.Downloader;
import com.tonyodev.fetch2core.FetchCoreUtils;
import com.tonyodev.fetch2core.FetchErrorStrings;
import com.tonyodev.fetch2core.FileSlice;
import com.tonyodev.fetch2core.FileSliceInfo;
import com.tonyodev.fetch2core.InterruptMonitor;
import com.tonyodev.fetch2core.Logger;
import com.tonyodev.fetch2core.OutputResourceWrapper;
import com.tonyodev.fetch2core.StorageResolver;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import timber.log.Timber;

/**
 * @since 2021-05-20
 */
public class ParallelFileDownloaderImpl implements FileDownloader {

    private Download initialDownload;
    private Downloader downloader;
    private long progressReportingIntervalMillis;
    private Logger logger;
    private NetworkInfoProvider networkInfoProvider;
    private boolean retryOnNetworkGain;
    private String fileTempDir;
    private boolean hashCheckingEnabled;
    private StorageResolver storageResolver;
    private boolean preAllocateFileOnCreation;
    private boolean interrupted = false;
    private boolean terminated = false;
    private FileDownloader.Delegate delegate = null;
    private DownloadInfo downloadInfo;
    private long downloaded = 0L;
    private long total = -1L;
    private boolean totalUnknown = false;
    private double averageDownloadedBytesPerSecond = 0.0f;
    private AverageCalculator movingAverageCalculator;
    private long estimatedTimeRemainingInMilliseconds = -1;
    private ExecutorService executorService = null;
    private int actionsCounter = 0;
    private int actionsTotal = 0;
    private final Object lock = new Object();
    private Throwable throwable = null;
    private List<FileSlice> fileSlices = new ArrayList<>();
    private OutputResourceWrapper outputResourceWrapper = null;
    private int totalDownloadBlocks = 0;


    public ParallelFileDownloaderImpl(Download initialDownload,
                                      Downloader downloader,
                                      long progressReportingIntervalMillis,
                                      Logger logger,
                                      NetworkInfoProvider networkInfoProvider,
                                      boolean retryOnNetworkGain,
                                      String fileTempDir,
                                      boolean hashCheckingEnabled,
                                      StorageResolver storageResolver,
                                      boolean preAllocateFileOnCreation) {

        this.initialDownload = initialDownload;
        this.downloader = downloader;
        this.progressReportingIntervalMillis = progressReportingIntervalMillis;
        this.logger = logger;
        this.networkInfoProvider = networkInfoProvider;
        this.retryOnNetworkGain = retryOnNetworkGain;
        this.fileTempDir = fileTempDir;
        this.hashCheckingEnabled = hashCheckingEnabled;
        this.storageResolver = storageResolver;
        this.preAllocateFileOnCreation = preAllocateFileOnCreation;

        downloadInfo = getDownloadInfo();
        movingAverageCalculator = new AverageCalculator(5);
    }

    @Override
    public boolean getInterrupted() {
        return interrupted;
    }

    @Override
    public void setInterrupted(boolean interrupted) {
        this.interrupted = interrupted;
        if (null != delegate && delegate instanceof FileDownloaderDelegate) {
            ((FileDownloaderDelegate) delegate).setInterrupted(interrupted);
        }
    }

    @Override
    public boolean getTerminated() {
        return terminated;
    }

    @Override
    public void setTerminated(boolean terminated) {
        this.terminated = terminated;
        if (null != delegate && delegate instanceof FileDownloaderDelegate) {
            ((FileDownloaderDelegate) delegate).setInterrupted(terminated);
        }
    }

    @Override
    public boolean getCompletedDownload() {
        return isDownloadComplete();
    }

    @Override
    public Delegate getDelegate() {
        return delegate;
    }

    @Override
    public void setDelegate(Delegate delegate) {
        this.delegate = delegate;
    }

    private DownloadInfo getDownloadInfo() {
        if (downloadInfo == null && delegate != null) {
            downloadInfo = TypeConverterExtensions.toDownloadInfo(initialDownload, delegate.getNewDownloadInfoInstance());
        }
        return downloadInfo;
    }

    @Override
    public Download getDownload() {
        getDownloadInfo();
        getDownloadInfo().setDownloaded(downloaded);
        getDownloadInfo().setTotal(total);
        return getDownloadInfo();
    }

    @Override
    public void run() {
        Downloader.Response openingResponse = null;
        try {
            downloaded = initialDownload.getDownloaded();
            total = initialDownload.getTotal();
            getDownloadInfo().setDownloaded(downloaded);
            getDownloadInfo().setTotal(total);
            Downloader.ServerRequest openingRequest;
            if (downloader.getHeadRequestMethodSupported(FetchUtils.getRequestForDownload(initialDownload))) {
                openingRequest = FetchUtils.getRequestForDownload(initialDownload, FetchCoreUtils.HEAD_REQUEST_METHOD);
            } else {
                openingRequest = FetchUtils.getRequestForDownload(initialDownload);
            }
            openingResponse = downloader.execute(openingRequest, interruptMonitor);
            if (openingResponse != null) {
                setIsTotalUnknown(openingResponse);
            }
            if (null != openingResponse && !interrupted && !terminated && openingResponse.isSuccessful() == true) {
                total = openingResponse.getContentLength();
                if (totalUnknown || total > 0) {
                    downloaded = 0;
                    getDownloadInfo().setDownloaded(downloaded);
                    getDownloadInfo().setTotal(total);
                    fileSlices = getFileSliceList(openingResponse.getAcceptsRanges(), openingRequest);
                    totalDownloadBlocks = fileSlices.size();
                    try {
                        downloader.disconnect(openingResponse);
                    } catch (Exception e) {
                        logger.e("FileDownloader", e);
                    }
                    List<FileSlice> sliceFileDownloadsList = new ArrayList<>();
                    for (FileSlice fileSlice : fileSlices) {
                        if (!fileSlice.isDownloaded()) {
                            sliceFileDownloadsList.add(fileSlice);
                        }
                    }

                    if (!interrupted && !terminated) {
                        getDownloadInfo().setDownloaded(downloaded);
                        getDownloadInfo().setTotal(total);
                        List<DownloadBlock> downloadBlocks = new ArrayList<>();
                        if (null != fileSlices && fileSlices.size() > 0) {
                            for (FileSlice fileSlice : fileSlices) {
                                DownloadBlockInfo downloadBlock = new DownloadBlockInfo();
                                downloadBlock.setDownloadId(fileSlice.getId());
                                downloadBlock.setBlockPosition(fileSlice.getPosition());
                                downloadBlock.setDownloadedBytes(fileSlice.getDownloaded());
                                downloadBlock.setStartByte(fileSlice.getStartBytes());
                                downloadBlock.setEndByte(fileSlice.getEndBytes());
                                downloadBlocks.add(downloadBlock);
                            }
                        }

                        if (!interrupted && !terminated) {
                            getDownloadInfo().setEtaInMilliSeconds(-1);
                            getDownloadInfo().setDownloadedBytesPerSecond(-1);
                            if (null != delegate) {
                                delegate.onStarted(getDownloadInfo(), downloadBlocks, totalDownloadBlocks);
                            }
                            if (null != downloadBlocks && downloadBlocks.size() > 0) {
                                for (DownloadBlock downloadBlock : downloadBlocks) {
                                    if (null != delegate) {
                                        delegate.onDownloadBlockUpdated(getDownloadInfo(), downloadBlock, totalDownloadBlocks);
                                    }
                                }
                            }
                        }
                        if (null != sliceFileDownloadsList && sliceFileDownloadsList.size() > 0) {
                            executorService = Executors.newFixedThreadPool(sliceFileDownloadsList.size());
                        }
                        downloadSliceFiles(openingRequest, sliceFileDownloadsList);
                        waitAndPerformProgressReporting();
                        getDownloadInfo().setDownloaded(downloaded);
                        getDownloadInfo().setTotal(total);
                    }
                } else {
                    throw new FetchException(FetchErrorStrings.EMPTY_RESPONSE_BODY);
                }
            } else if (openingResponse == null && !interrupted && !terminated && !isDownloadComplete()) {
                throw new FetchException(FetchErrorStrings.EMPTY_RESPONSE_BODY);
            } else if (null != openingResponse && !openingResponse.isSuccessful() && !interrupted && !terminated && !isDownloadComplete()) {
                throw new FetchException(FetchErrorStrings.RESPONSE_NOT_SUCCESSFUL);
            } else if (!interrupted && !terminated && !isDownloadComplete()) {
                throw new FetchException(FetchErrorStrings.UNKNOWN_ERROR);
            }
            getDownloadInfo().setDownloaded(downloaded);
            getDownloadInfo().setTotal(total);
            throwExceptionIfFound();
            if (!isDownloadComplete() && !terminated && !interrupted) {
                if (null != delegate) {
                    delegate.saveDownloadProgress(getDownloadInfo());
                }
                getDownloadInfo().setEtaInMilliSeconds(estimatedTimeRemainingInMilliseconds);
                getDownloadInfo().setDownloadedBytesPerSecond(getAverageDownloadedBytesPerSecond());
                if (null != delegate) {
                    delegate.onProgress(getDownloadInfo(), getDownloadInfo().getEtaInMilliSeconds(),
                        getDownloadInfo().getDownloadedBytesPerSecond());
                    Timber.d("276 getDownloadInfo:" + getDownloadInfo().getProgress());
                }
            } else if (!interrupted && !terminated && isDownloadComplete()) {
                if (!totalUnknown) {
                    long fileSlicesTotal = 0L;
                    if (null != fileSlices && fileSlices.size() > 0) {
                        for (FileSlice slice : fileSlices) {
                            fileSlicesTotal += slice.getDownloaded();
                        }
                    }

                    if (fileSlicesTotal != total) {
                        throwable = new FetchException(FetchErrorStrings.DOWNLOAD_INCOMPLETE);
                        throwExceptionIfFound();
                    }
                } else {
                    total = downloaded;
                    getDownloadInfo().setDownloaded(downloaded);
                    getDownloadInfo().setTotal(total);
                }
                if (!terminated && !interrupted && null != delegate) {
                    delegate.saveDownloadProgress(getDownloadInfo());
                }
                if (hashCheckingEnabled) {
                    if (openingResponse != null && downloader.verifyContentHash(openingResponse.getRequest(), openingResponse.getHash())) {
                        FetchUtils.deleteAllInFolderForId(getDownloadInfo().getId(), fileTempDir);
                        if (!interrupted && !terminated) {
                            getDownloadInfo().setEtaInMilliSeconds(estimatedTimeRemainingInMilliseconds);
                            getDownloadInfo().setDownloadedBytesPerSecond(getAverageDownloadedBytesPerSecond());
                            Download completedDownload = getDownloadInfo().copy();
                            if (null != delegate) {
                                delegate.onProgress(getDownloadInfo(), getDownloadInfo().getEtaInMilliSeconds(),
                                    getDownloadInfo().getDownloadedBytesPerSecond());
                                Timber.d("313 getDownloadInfo:" + getDownloadInfo().getProgress());
                            }
                            getDownloadInfo().setEtaInMilliSeconds(-1);
                            getDownloadInfo().setDownloadedBytesPerSecond(-1);
                            if (null != delegate) {
                                delegate.onComplete(completedDownload);
                            }

                        }
                    } else {
                        FetchUtils.deleteAllInFolderForId(getDownloadInfo().getId(), fileTempDir);
                        throw new FetchException(FetchErrorStrings.INVALID_CONTENT_HASH);
                    }
                } else {
                    FetchUtils.deleteAllInFolderForId(getDownloadInfo().getId(), fileTempDir);
                    if (!interrupted && !terminated) {
                        getDownloadInfo().setEtaInMilliSeconds(estimatedTimeRemainingInMilliseconds);
                        getDownloadInfo().setDownloadedBytesPerSecond(getAverageDownloadedBytesPerSecond());
                        Download completedDownload = getDownloadInfo().copy();
                        if (null != delegate) {
                            delegate.onProgress(getDownloadInfo(), getDownloadInfo().getEtaInMilliSeconds(),
                                getDownloadInfo().getDownloadedBytesPerSecond());
                            Timber.d("335 getDownloadInfo:" + getDownloadInfo().getProgress());
                        }
                        getDownloadInfo().setEtaInMilliSeconds(-1);
                        getDownloadInfo().setDownloadedBytesPerSecond(-1);
                        if (null != delegate) {
                            delegate.onComplete(completedDownload);
                        }
                    }
                }
            }
        } catch (Throwable e) {
            if (!interrupted && !terminated) {
                logger.e("FileDownloader download:" + getDownload(), e);
                Error error = FetchErrorUtils.getErrorFromThrowable(e);
                error.setThrowable(e);
                if (openingResponse != null) {
                    error.setHttpResponse(FetchCoreUtils.copyDownloadResponseNoStream(openingResponse));
                }
                if (retryOnNetworkGain) {
                    boolean disconnectDetected = !networkInfoProvider.isNetworkAvailable();
                    for (int i = 0; i < 10; i++) {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e2) {
                            logger.e("FileDownloader", e2);
                            break;
                        }
                        if (!networkInfoProvider.isNetworkAvailable()) {
                            disconnectDetected = true;
                            break;
                        }
                    }
                    if (disconnectDetected) {
                        error = Error.NO_NETWORK_CONNECTION;
                    }
                }
                getDownloadInfo().setDownloaded(downloaded);
                getDownloadInfo().setTotal(total);
                getDownloadInfo().setError(error);
                if (!terminated && !interrupted) {
                    getDownloadInfo().setEtaInMilliSeconds(-1);
                    getDownloadInfo().setDownloadedBytesPerSecond(-1);
                    if (null != delegate) {
                        delegate.onError(getDownloadInfo(), error, e);
                    }
                }
            }
        } finally {
            try {
                if (null != executorService) {
                    executorService.shutdown();
                }
            } catch (Exception e) {
                logger.e("FileDownloader", e);
            }
            try {
                if (null != outputResourceWrapper) {
                    outputResourceWrapper.close();
                }
            } catch (Exception e) {
                logger.e("FileDownloader", e);
            }
            if (openingResponse != null) {
                try {
                    downloader.disconnect(openingResponse);
                } catch (Exception e) {
                    logger.e("FileDownloader", e);
                }
            }
            terminated = true;
        }
    }

    private List<FileSlice> getFileSliceList(boolean acceptsRanges, Downloader.ServerRequest request) {
        if (!storageResolver.fileExists(getDownloadInfo().getFile())) {
            FetchUtils.deleteAllInFolderForId(getDownloadInfo().getId(), fileTempDir);
        }
        int previousSliceSize = FetchUtils.getPreviousSliceCount(getDownloadInfo().getId(), fileTempDir);

        if (acceptsRanges && !totalUnknown) {
            FileSliceInfo fileSliceInfo = getChuckInfo(request);
            if (previousSliceSize != fileSliceInfo.getSlicingCount()) {
                FetchUtils.deleteAllInFolderForId(getDownloadInfo().getId(), fileTempDir);
            }
            FetchUtils.saveCurrentSliceCount(getDownloadInfo().getId(), fileSliceInfo.getSlicingCount(), fileTempDir);
            long counterBytes = 0L;
            List<FileSlice> fileSlices = new ArrayList<>();
            for (int position = 1; position < fileSliceInfo.getSlicingCount() + 1; position++) {
                if (!interrupted && !terminated) {
                    long startBytes = counterBytes;
                    long endBytes;
                    if (fileSliceInfo.getSlicingCount() == position) {
                        endBytes = total;
                    } else {
                        endBytes = counterBytes + fileSliceInfo.getBytesPerFileSlice();
                    }
                    counterBytes = endBytes;

                    FileSlice fileSlice = new FileSlice(
                        getDownloadInfo().getId(),
                        position,
                        startBytes,
                        endBytes,
                        FetchUtils.getSavedDownloadedInfo(getDownloadInfo().getId(), position, fileTempDir)
                    );
                    downloaded += fileSlice.getDownloaded();
                    fileSlices.add(fileSlice);
                } else {
                    break;
                }
            }
            return fileSlices;
        } else {
            if (previousSliceSize != 1) {
                FetchUtils.deleteAllInFolderForId(getDownloadInfo().getId(), fileTempDir);
            }
            FetchUtils.saveCurrentSliceCount(getDownloadInfo().getId(), 1, fileTempDir);
            FileSlice fileSlice = new FileSlice(getDownloadInfo().getId(), 1, 0, total,
                FetchUtils.getSavedDownloadedInfo(getDownloadInfo().getId(), 1, fileTempDir));
            downloaded += fileSlice.getDownloaded();
            List<FileSlice> fileSlices = new ArrayList<>();
            fileSlices.add(fileSlice);
            return fileSlices;
        }
    }

    private void waitAndPerformProgressReporting() {
        long reportingStopTime;
        long downloadSpeedStopTime;
        long downloadedBytesPerSecond = downloaded;
        long reportingStartTime = System.nanoTime();
        long downloadSpeedStartTime = System.nanoTime();
        while (actionsCounter != actionsTotal && !interrupted && !terminated) {
            getDownloadInfo().setDownloaded(downloaded);
            getDownloadInfo().setTotal(total);
            downloadSpeedStopTime = System.nanoTime();
            boolean downloadSpeedCheckTimeElapsed = FetchCoreUtils.hasIntervalTimeElapsed(downloadSpeedStartTime,
                downloadSpeedStopTime, Defaults.DEFAULT_DOWNLOAD_SPEED_REPORTING_INTERVAL_IN_MILLISECONDS);

            if (downloadSpeedCheckTimeElapsed) {
                downloadedBytesPerSecond = downloaded - downloadedBytesPerSecond;
                movingAverageCalculator.add((double) downloadedBytesPerSecond);
                averageDownloadedBytesPerSecond =
                    movingAverageCalculator.getMovingAverageWithWeightOnRecentValues();
                estimatedTimeRemainingInMilliseconds = FetchCoreUtils.calculateEstimatedTimeRemainingInMilliseconds(
                    downloaded,
                    total,
                    getAverageDownloadedBytesPerSecond());
                downloadedBytesPerSecond = downloaded;
            }
            reportingStopTime = System.nanoTime();
            boolean hasReportingTimeElapsed = FetchCoreUtils.hasIntervalTimeElapsed(reportingStartTime,
                reportingStopTime, progressReportingIntervalMillis);
            if (hasReportingTimeElapsed) {
                synchronized (lock) {
                    if (!interrupted && !terminated) {
                        getDownloadInfo().setDownloaded(downloaded);
                        getDownloadInfo().setTotal(total);
                        if (null != delegate) {
                            delegate.saveDownloadProgress(getDownloadInfo());
                        }
                        getDownloadInfo().setEtaInMilliSeconds(estimatedTimeRemainingInMilliseconds);
                        getDownloadInfo().setDownloadedBytesPerSecond(getAverageDownloadedBytesPerSecond());
                        if (null != delegate) {
                            delegate.onProgress(getDownloadInfo(), getDownloadInfo().getEtaInMilliSeconds(),
                                getDownloadInfo().getDownloadedBytesPerSecond());
                            Timber.d("500 getDownloadInfo:" + getDownloadInfo().getProgress());
                        }
                    }
                }
                reportingStartTime = System.nanoTime();
            }
            if (downloadSpeedCheckTimeElapsed) {
                downloadSpeedStartTime = System.nanoTime();
            }
            try {
                Thread.sleep(progressReportingIntervalMillis);
            } catch (InterruptedException e) {
                logger.e("FileDownloader", e);
            }
        }
    }

    private void downloadSliceFiles(Downloader.ServerRequest request, List<FileSlice> fileSlicesDownloadsList) throws IOException, DataAbilityRemoteException {
        actionsCounter = 0;
        actionsTotal = fileSlicesDownloadsList.size();
        if (!storageResolver.fileExists(request.getFile())) {
            storageResolver.createFile(request.getFile(), initialDownload.getEnqueueAction() == EnqueueAction.INCREMENT_FILE_NAME);
        }
        if (preAllocateFileOnCreation) {
            storageResolver.preAllocateFile(request.getFile(), getDownloadInfo().getTotal());
        }
        outputResourceWrapper = storageResolver.getRequestOutputResourceWrapper(request);
        if (null != outputResourceWrapper) {
            outputResourceWrapper.setWriteOffset(0);
        }
        for (FileSlice fileSlice : fileSlicesDownloadsList) {
            if (!interrupted && !terminated) {
                if (null != executorService) {
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.currentThread().setName(getDownloadInfo().getNamespace() + "-" + getDownloadInfo().getId() + "-Slice-" + fileSlice.getPosition());
                            } catch (Exception e) {

                            }
                            DownloadBlockInfo downloadBlock = new DownloadBlockInfo();
                            downloadBlock.setDownloadId(fileSlice.getId());
                            downloadBlock.setBlockPosition(fileSlice.getPosition());
                            downloadBlock.setDownloadedBytes(fileSlice.getDownloaded());
                            downloadBlock.setStartByte(fileSlice.getStartBytes());
                            downloadBlock.setEndByte(fileSlice.getEndBytes());

                            Downloader.ServerRequest downloadRequest = FetchUtils.getRequestForDownload(getDownloadInfo(), fileSlice.getStartBytes() + fileSlice.getDownloaded(), fileSlice.getPosition() + 1);
                            Downloader.Response downloadResponse = null;
                            RandomAccessFile saveRandomAccessFile = null;
                            try {
                                File file = null;
                                try {
                                    file = FetchCoreUtils.getFile(FetchUtils.getDownloadedInfoFilePath(fileSlice.getId(), fileSlice.getPosition(), fileTempDir));
                                    saveRandomAccessFile = new RandomAccessFile(file, "rw");
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                if (downloadRequest.getUrl().contains("big_buck_bunny_720p_stereo")) {
                                    Timber.d("downloadRequest.getUrl(): " + downloadRequest.getUrl());
                                }

                                downloadResponse = downloader.execute(downloadRequest, interruptMonitor);
                                if (null != downloadResponse && downloadResponse.isSuccessful() && !terminated && !interrupted) {
                                    long reportingStopTime;
                                    int bufferSize = downloader.getRequestBufferSize(downloadRequest);
                                    byte[] buffer = new byte[bufferSize];
                                    int read = -1;
                                    try {
                                        if (null != downloadResponse.getByteStream()) {
                                            read = downloadResponse.getByteStream().read(buffer, 0, bufferSize);
                                        }
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }

                                    long naturalEnd;
                                    if (fileSlice.getEndBytes() < 1) {
                                        naturalEnd = 0;
                                    } else {
                                        naturalEnd = fileSlice.getEndBytes();
                                    }
                                    long remainderBytes = naturalEnd - (fileSlice.getStartBytes() + fileSlice.getDownloaded());
                                    long reportingStartTime = System.nanoTime();
                                    int streamBytes;
                                    long seekPosition;
                                    while ((totalUnknown || remainderBytes > 0L) && read != -1 && !interrupted && !terminated) {
                                        if (totalUnknown || read <= remainderBytes) {
                                            streamBytes = read;
                                        } else {
                                            read = -1;
                                            streamBytes = (int) remainderBytes;
                                        }
                                        System.out.println("597 streamBytes:" + streamBytes + ",getLastPath:" + request.getFileUri().getLastPath());
                                        seekPosition = fileSlice.getStartBytes() + fileSlice.getDownloaded();
                                        synchronized (lock) {
                                            if (!interrupted && !terminated) {
                                                if (null != outputResourceWrapper) {
                                                    try {
                                                        outputResourceWrapper.setWriteOffset(seekPosition);
                                                        outputResourceWrapper.write(buffer, 0, streamBytes);
                                                        if (!interrupted && !terminated) {
                                                            fileSlice.setDownloaded(fileSlice.getDownloaded() + streamBytes);
                                                            saveRandomAccessFile.seek(0);
                                                            saveRandomAccessFile.setLength(0);
                                                            saveRandomAccessFile.writeLong(fileSlice.getDownloaded());
                                                            downloaded += streamBytes;
                                                        }
                                                    } catch (IOException e) {
                                                        e.printStackTrace();
                                                    }
                                                }

                                                reportingStopTime = System.nanoTime();
                                                boolean hasReportingTimeElapsed = FetchCoreUtils.hasIntervalTimeElapsed(reportingStartTime,
                                                    reportingStopTime, progressReportingIntervalMillis);
                                                if (hasReportingTimeElapsed) {
                                                    if (!interrupted && !terminated) {
                                                        downloadBlock.setDownloadedBytes(fileSlice.getDownloaded());
                                                        if (null != delegate) {
                                                            delegate.onDownloadBlockUpdated(getDownloadInfo(), downloadBlock, totalDownloadBlocks);
                                                        }
                                                    }
                                                    reportingStartTime = System.nanoTime();
                                                }
                                            }
                                        }
                                        if (!interrupted && !terminated && read != -1) {
                                            InputStream byteStream = downloadResponse.getByteStream();
                                            if (byteStream == null) {
                                                read = -1;
                                            } else {
                                                read = downloadResponse.getByteStream().read(buffer, 0, bufferSize);
                                            }
                                            if (!totalUnknown) {
                                                remainderBytes = naturalEnd - (fileSlice.getStartBytes() + fileSlice.getDownloaded());
                                            }
                                        }
                                        /**
                                         * 修复偶发downloaded文件剩余字节未完成下载问题，
                                         * read==-1代表读取不到有效字节，diff代表产生差异时剩余字节
                                         */
                                        if (read == -1 && downloaded != total) {
                                            long diff = total - downloaded;
                                            if (diff <= 10) {
                                                read = (int) diff;
                                                remainderBytes = diff;
                                            }
                                        }
                                    }
                                } else if (downloadResponse == null && !interrupted && !terminated) {
                                    throw new FetchException(FetchErrorStrings.EMPTY_RESPONSE_BODY);
                                } else if (downloadResponse != null && !downloadResponse.isSuccessful() && !interrupted && !terminated) {
                                    throw new FetchException(FetchErrorStrings.RESPONSE_NOT_SUCCESSFUL);
                                } else if (!interrupted && !terminated) {
                                    throw new FetchException(FetchErrorStrings.UNKNOWN_ERROR);
                                }
                            } catch (Exception e) {
                                logger.e("FileDownloader downloads slice $fileSlice", e);
                                throwable = e;
                            } finally {
                                try {
                                    if (downloadResponse != null) {
                                        downloader.disconnect(downloadResponse);
                                    }
                                } catch (Exception e) {
                                    logger.e("FileDownloader", e);
                                }
                                try {
                                    if (null != saveRandomAccessFile) {
                                        saveRandomAccessFile.close();
                                    }
                                } catch (Exception e) {
                                    logger.e("FileDownloader", e);
                                }
                                incrementActionCompletedCount();
                            }
                            System.out.println("674 downloaded:" + downloaded + ",getLastPath:" + request.getFileUri().getLastPath());
                        }
                    });
                }
            } else {
                break;
            }
        }

    }


    private FileSliceInfo getChuckInfo(Downloader.ServerRequest request) {
        int fileSliceSize = downloader == null ? downloader.getFileSlicingCount(request, total) : Defaults.DEFAULT_FILE_SLICE_NO_LIMIT_SET;
        return FetchUtils.getFileSliceInfo(fileSliceSize, total);
    }

    private void incrementActionCompletedCount() {
        synchronized (lock) {
            actionsCounter += 1;
        }
    }

    private boolean isDownloadComplete() {
        return ((downloaded > 0 && total > 0) || totalUnknown) && (downloaded >= total);
    }

    private void setIsTotalUnknown(Downloader.Response response) {
        if (response.isSuccessful() && response.getContentLength() == -1L) {
            totalUnknown = true;
        }
    }

    private InterruptMonitor interruptMonitor = new InterruptMonitor() {
        @Override
        public boolean isInterrupted() {
            return interrupted;
        }
    };

    private void throwExceptionIfFound() throws Throwable {
        Throwable exception = throwable;
        if (exception != null) {
            throw exception;
        }
    }

    private long getAverageDownloadedBytesPerSecond() {
        if (averageDownloadedBytesPerSecond < 1) {
            return 0L;
        }
        return (long) Math.ceil(averageDownloadedBytesPerSecond);
    }

}
